/**
 * 视频投射
 * @param {Cesium.Viewer} viewer Cesium三维视窗。
 * @param {Object} options 选项。
 * @param {Cesium.Cartesian3} options.viewPosition 观测点位置。
 * @param {Cesium.Cartesian3} options.viewPositionEnd 最远观测点位置（如果设置了观测距离，这个属性可以不设置）。
 * @param {Number} options.viewDistance 观测距离（单位`米`，默认值100）。
 * @param {Number} options.viewHeading 航向角（单位`度`，默认值0）。
 * @param {Number} options.viewPitch 俯仰角（单位`度`，默认值0）。
 * @param {Number} options.horizontalViewAngle 水平夹角（单位`度`，默认值90）。
 * @param {Number} options.verticalViewAngle 垂直夹角（单位`度`，默认值60）。
 * @param {Boolean} options.enabled 阴影贴图是否可用。
 * @param {Boolean} options.softShadows 是否启用柔和阴影。
 * @param {Number} options.size 每个阴影贴图的大小。
 * @param {String} options.playUrl 传入播放地址，自动创建元素，用于一般mp4格式。
 * @param {DOM} options.videoElement 直接传入video元素，用于各种格式。
 * @param {Boolean} options.isLive 当前播放视频是否为直播。
 */
import CustomFrustumPrimitive from "./CustomFrustumPrimitive.js";
class VideoShed {
  constructor(viewer, options) {
    this.viewer = viewer;
    this.init(options);
  }

  init(options) {
    this.viewPosition = options.viewPosition;
    this.viewPositionEnd = options.viewPositionEnd;
    this.viewDistance = this.viewPositionEnd ? Cesium.Cartesian3.distance(this.viewPosition, this.viewPositionEnd) : (options.viewDistance || 100.0);
    this.viewHeading = this.viewPositionEnd ? getHeading(this.viewPosition, this.viewPositionEnd) : (options.viewHeading || 0.0);
    this.viewPitch = this.viewPositionEnd ? getPitch(this.viewPosition, this.viewPositionEnd) : (options.viewPitch || 0.0);
    this.horizontalViewAngle = options.horizontalViewAngle || 90.0;
    this.verticalViewAngle = options.verticalViewAngle || 60.0;
    this.enabled = (typeof options.enabled === "boolean") ? options.enabled : true;
    this.softShadows = (typeof options.softShadows === "boolean") ? options.softShadows : true;
    this.size = options.size || 2048;
    this.playUrl = options.playUrl;
    this.videoElement = options.videoElement;
    this.isLive = Boolean(options.isLive);

    this.clear();
    this.add();
  }

  updateView(position, heading, pitch) {
    this.viewPosition = position;
    this.viewHeading = heading;
    this.viewPitch = pitch;
    this.lightCamera.setView({
      destination: position,
      orientation: {
        heading: Cesium.Math.toRadians(heading),
        pitch: Cesium.Math.toRadians(pitch),
        roll: 0
      }
    });
    this.frustumOutline.position = position;
  }

  add() {
    this.activeVideo();
    this.createLightCamera();
    this.createShadowMap();
    this.createPostStage();
    this.drawFrustumOutline();
    this.viewer.scene.primitives.add(this);
  }

  clear() {
    if (this.frustumOutline) {
      this.frustumOutline.destroy();
      this.frustumOutline = null;
    }
    if (this.postStage) {
      this.viewer.scene.postProcessStages.remove(this.postStage);
      this.postStage = null;
    }
    this.deActiveVideo();
    this.viewer.scene.primitives.remove(this);
  }

  createLightCamera() {
    this.lightCamera = new Cesium.Camera(this.viewer.scene);
    this.lightCamera.position = this.viewPosition;
    this.lightCamera.frustum.near = 3;
    this.lightCamera.frustum.far = this.viewDistance;
    const hr = Cesium.Math.toRadians(this.horizontalViewAngle);
    const vr = Cesium.Math.toRadians(this.verticalViewAngle);
    const aspectRatio =
      (this.viewDistance * Math.tan(hr / 2) * 2) /
      (this.viewDistance * Math.tan(vr / 2) * 2);
    this.lightCamera.frustum.aspectRatio = aspectRatio;
    if (hr > vr) {
      this.lightCamera.frustum.fov = hr;
    } else {
      this.lightCamera.frustum.fov = vr;
    }
    this.lightCamera.setView({
      destination: this.viewPosition,
      orientation: {
        heading: Cesium.Math.toRadians(this.viewHeading || 0),
        pitch: Cesium.Math.toRadians(this.viewPitch || 0),
        roll: 0
      }
    });
  }

  createShadowMap() {
    this.shadowMap = new Cesium.ShadowMap({
      context: this.viewer.scene.context,
      lightCamera: this.lightCamera,
      enabled: this.enabled,
      isPointLight: false,
      pointLightRadius: this.viewDistance,
      cascadesEnabled: false,
      size: this.size,
      softShadows: this.softShadows,
      normalOffset: false,
      fromLightSource: false
    });
  }

  createPostStage() {
    const postStage = new Cesium.PostProcessStage({
      fragmentShader: `
        uniform sampler2D colorTexture;
        uniform sampler2D depthTexture;
        uniform sampler2D videoTexture;
        varying vec2 v_textureCoordinates;
        uniform mat4 camera_projection_matrix;
        uniform mat4 camera_view_matrix;
        uniform mat4 shadowMap_matrix;
        vec4 toEye(in vec2 uv,in float depth){
          vec2 xy=vec2((uv.x*2.-1.),(uv.y*2.-1.));
          vec4 posInCamera=czm_inverseProjection*vec4(xy,depth,1.);
          posInCamera=posInCamera/posInCamera.w;
          return posInCamera;
        }
        float getDepth(in vec4 depth){
          float z_window=czm_unpackDepth(depth);
          z_window=czm_reverseLogDepth(z_window);
          float n_range=czm_depthRange.near;
          float f_range=czm_depthRange.far;
          return(2.*z_window-n_range-f_range)/(f_range-n_range);
        }
        bool visible(in vec4 result) {
          result.x/=result.w;
          result.y/=result.w;
          result.z/=result.w;
          return result.x>=-1.&&result.x<=1.
          &&result.y>=-1.&&result.y<=1.
          &&result.z>=-1.&&result.z<=1.;
        }
        void main(){
          // 釉色 = 结构二维(颜色纹理, 纹理坐标)
          vec4 color = texture2D(colorTexture, v_textureCoordinates);
          // 深度 = 获取深度(结构二维(深度纹理, 纹理坐标))
          vec4 curDepth = texture2D(depthTexture, v_textureCoordinates);
          if (curDepth.r >= 1.0) {
            gl_FragColor = color;
            return;
          }
          float depth = getDepth(curDepth);
          // 视角 = (纹理坐标, 深度)
          vec4 viewPos = toEye(v_textureCoordinates, depth);
          // 世界坐标
          vec4 wordPos = czm_inverseView * viewPos;
          // 虚拟相机中坐标
          vec4 vcPos = camera_view_matrix * wordPos;
          // 透视投影
          vec4 posInEye = camera_projection_matrix * vcPos;
          // 阴影坐标
          vec4 shadowPosition = shadowMap_matrix * viewPos;
          shadowPosition /= shadowPosition.w;
          if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) {
            gl_FragColor = color;
            return;
          }
          // 视频贴图
          vec4 videoColor = texture2D(videoTexture, shadowPosition.xy);
          // 可视区颜色
          if(visible(posInEye)){
            gl_FragColor = mix(color, vec4(videoColor.xyz, 1.0), 1.0);
          }else {
            gl_FragColor = color;
          }
        }
      `,
      uniforms: {
        videoTexture: () => {
          return this.videoTexture;
        },
        shadowMap_textureCube: () => {
          return Reflect.get(this.shadowMap, "_shadowMapTexture");
        },
        shadowMap_matrix: () => {
          return Reflect.get(this.shadowMap, "_shadowMapMatrix");
        },
        camera_projection_matrix: this.lightCamera.frustum.projectionMatrix,
        camera_view_matrix: this.lightCamera.viewMatrix,
      }
    });
    this.postStage = this.viewer.scene.postProcessStages.add(postStage);
  }

  drawFrustumOutline() {
    this.frustumOutline = this.viewer.scene.primitives.add(new CustomFrustumPrimitive({lightCamera: this.lightCamera}));
  }

  activeVideo() {
    let videoElement = this.videoElement || this.createVideoElement(this.playUrl);
    this.activeVideoListener || (this.activeVideoListener = () => {
      this.videoTexture && this.videoTexture.destroy();
      this.videoTexture = new Cesium.Texture({
        context: this.viewer.scene.context,
        source: videoElement,
        width: 1,
        height: 1,
        pixelFormat: Cesium.PixelFormat.RGBA,
        pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE
      });
    });
    this.viewer.clock.onTick.addEventListener(this.activeVideoListener);
    if(this.isLive) { //直播视频开启视频同步
      this.synchronizer = new Cesium.VideoSynchronizer({
        clock: this.viewer.clock,
        element: videoElement,
      });
    }
  }

  deActiveVideo() {
    if (this.activeVideoListener) {
      this.viewer.clock.onTick.removeEventListener(this.activeVideoListener);
    }
    if (this.synchronizer) {
      this.synchronizer.destroy();
    }
  }

  createVideoElement(url) {
    let video = document.createElement("video");
    video.src = url;
    video.setAttribute("autoplay", true);
    video.setAttribute("loop", true);
    video.setAttribute("muted", true);
    video.setAttribute("crossorigin", "anonymous");
    return video;
  }

  // 实现primitive内部更新方法
  update(e) {
    this.shadowMap && this.viewer.scene.frameState.shadowMaps.push(this.shadowMap); //必须添加，否则无视频效果
  }
  // 实现primitive内部销毁方法
  destroy() {}
}

function getHeading(fromPosition, toPosition) {
  let finalPosition = new Cesium.Cartesian3();
  let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
  Cesium.Matrix4.inverse(matrix4, matrix4);
  Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
  Cesium.Cartesian3.normalize(finalPosition, finalPosition);
  return Cesium.Math.toDegrees(Math.atan2(finalPosition.x, finalPosition.y));
}

function getPitch(fromPosition, toPosition) {
  let finalPosition = new Cesium.Cartesian3();
  let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
  Cesium.Matrix4.inverse(matrix4, matrix4);
  Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
  Cesium.Cartesian3.normalize(finalPosition, finalPosition);
  return Cesium.Math.toDegrees(Math.asin(finalPosition.z));
}

Cesium.VideoShed = VideoShed;